package com.gitee.wsl.struct.router

import com.gitee.wsl.ext.any.ifNullValue
import com.gitee.wsl.func.factory.Factory2

/**
 * 主要用于一些类似 url 路由类 型结构处理
 *
 *    val router= router {
 *        defaultHandler { uriResource, urlParams ->
 *            print("no find url")
 *        }
 *        on("http://test.com") {uriResource, urlParams ->
 *            print("go to  test.com")
 *        }
 *    }
 *    router.process("http://test.com")
 *
 * 入口定义
 */

fun interface CallHandler<Context> {
    fun call(context: Context,resource:UriResource<Context>,urlParams: Map<String, String>)
}

 fun interface CallHandlerFactory<Context>: Factory2<Unit, CallHandler<Context>>

 open class FactoryCallHandler<Context>(val factory:CallHandlerFactory<Context> ):CallHandler<Context>{
       override fun call(
           context: Context,
           resource: UriResource<Context>,
           urlParams: Map<String, String>
       ) {
           factory.create(Unit).call(context, resource, urlParams)
       }
}

interface RouterScope<Context>{
    fun on(url: String,handler:CallHandler<Context>)

    fun on(url:String,factory: CallHandlerFactory<Context>)=on(url,FactoryCallHandler(factory))

    fun on(dir:String,subRouterBlock: RouterScope<Context>.()->Unit)
}

interface UriRouterScope<Context>:RouterScope<Context>{

    fun defaultHandler(handler:CallHandler<Context>)

    fun config(configRouter:UriRouterManager<Context>.()->Unit)

}

class RouterScopeImp<Context>(val baseDir:String,val rooterManager:UriRouterManager<Context> ):RouterScope<Context>{
    override fun on(url: String, handler: CallHandler<Context>) {
        rooterManager.addRoute(buildSub(baseDir,url), handler )
    }

    override fun on(dir: String, subRouterBlock: RouterScope<Context>.() -> Unit) {
        val sub=RouterScopeImp(buildSub(baseDir,dir),rooterManager)
        subRouterBlock(sub)
    }


}

fun sampleRouter(routerConfig:UriRouterScope<Unit>.()->Unit):UriRouterManager<Unit>{
    val router=UriRouterManager<Unit>()
    routerConfig(object:UriRouterScope<Unit>{
        override fun defaultHandler(handler: CallHandler<Unit>) {
            router.setNotImplemented(handler)
        }

        override fun on(url: String, handler: CallHandler<Unit>) {
           router.addRoute(url,handler)
        }

        override fun on(dir: String, subRouterBlock: RouterScope<Unit>.()->Unit) {
            val sub=RouterScopeImp(buildSub("/",dir),router)
            subRouterBlock(sub)
        }

        override fun config(configRouter: UriRouterManager<Unit>.() -> Unit) {
            configRouter(router)
        }

    })
    return router
}

open class UriRouterManager<Context>(var router: Router<Context> = ProvidedPriorityRouter()) {

    /**
     * Search in the mappings if the given url matches some of the rules If
     * there are more than one marches returns the rule with less parameters
     * e.g. mapping 1 = /user/:id mapping 2 = /user/help if the incoming uri
     * is UriTemplate("http://www{.domain}{/path}{?var:5,col,map}{&var*:2,col:1*,map*") - mapping 2 is returned if the incoming
     * uri is www.example.com/user/3232 - mapping 1 is returned
     *
     * @param url
     * @return
     */
    fun process(context: Context,url: String, method: Method=Method.GET) {
        val work: String = normalizeUri(url)
        matchUri(work, method)?.let { uriResource->
            val params: Map<String, String> = uriResource.match(url)?: emptyMap()
            uriResource.process(context,params)
        }
    }

    fun process(context: Context,url: String, method: Method=Method.GET, params: Map<String, String>) {
        val work: String = normalizeUri(url)
        matchUri(work, method)?.process(context,params)
    }

    fun getParams(url: String, method: Method=Method.GET):Map<String, String>?{
        val work: String = normalizeUri(url)
        return matchUri(work, method)?.match(url)
    }

    private fun matchUri(workUri: String, method: Method=Method.GET): UriResource<Context>? {
        var uriResource: UriResource<Context>? =null
        var params: Map<String, String>? = null
        for (u in router.prioritizedRoutes) {
            if (u.method == method || u.method == Method.ALL) {
                params = u.match(workUri)
                if (params != null) {
                    uriResource = u
                    break
                }
            }
        }
        return uriResource
    }


     fun addRoute(url: String, handler:CallHandler<Context>?, method: Method=Method.GET, priority: Int=0, vararg initParameter: Any) {
        router.addRoute(url, method, priority, handler, *initParameter)
    }

    fun addRoute(url: String, handler:CallHandlerFactory<Context>?, method: Method=Method.GET, priority: Int=0, vararg initParameter: Any) {
        router.addRoute(url, method, priority, handler.ifNullValue(null,FactoryCallHandler(handler!!)), *initParameter)
    }

     fun removeRoute(url: String) {
        removeRoute(url, Method.GET)
    }

     fun removeRoute(url: String, method: Method=Method.GET) {
        router.removeRoute(url, method)
    }
    
    fun setNotImplemented(handler: CallHandler<Context>) {
        router.notImplemented=handler
    }

    fun addSubRoute(dir: String,subRoute:UriRouterManager<Context>){
        subRoute.router.prioritizedRoutes.forEach{
            addRoute("$dir/${it.baseUri}",it.handler,it.method,0,it.initParameter)
        }
    }

}

val PARAM_PATTERN = "(?<=(^|/)):[a-zA-Z0-9_-]+(?=(/|$))".toRegex()

//val PARAM_PATTERN = Pattern.compile("(?<=(^|/)):[a-zA-Z0-9_-]+(?=(/|$))")

const val PARAM_MATCHER = "([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=\\s]+)"

fun normalizeUri(value1: String): String {
    var value = value1
    if (value.startsWith("/")) {
        value = value.substring(1)
    }
    if (value.endsWith("/")) {
        value = value.substring(0, value.length - 1)
    }
    return value
}

fun createUriPattern(patternUri: String): String {
    /*var patternUri = patternUri1
    var start = 0
    var matcher: Matcher = PARAM_PATTERN.matcher(patternUri)
    while (matcher.find(start)) {
        patternUri = StringBuilder(patternUri.substring(0, matcher.start())) //
            .append(PARAM_MATCHER) //
            .append(patternUri.substring(matcher.end())).toString()
        start = matcher.start() + PARAM_MATCHER.length
        matcher = PARAM_PATTERN.matcher(patternUri)
    }
    return patternUri*/

    return patternUri.replace(PARAM_PATTERN){
        PARAM_MATCHER
    }
}

fun buildSub(dir:String,path: String):String{
    return if(path.startsWith("/")) "$dir$path" else "$dir/$path"
}

